package taskTracker.util;


import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Calendar;
import java.util.Date;
import java.util.Enumeration;
import java.util.Stack;
import java.util.Vector;

import javax.microedition.lcdui.Alert;
import javax.microedition.lcdui.AlertType;
import javax.microedition.rms.RecordEnumeration;
import javax.microedition.rms.RecordStore;
import javax.microedition.rms.RecordStoreException;
import javax.microedition.rms.RecordStoreFullException;
import javax.microedition.rms.RecordStoreNotFoundException;

import taskTracker.ui.lite.MainScreen;


/**
 * Some utilities to be used with string
 * @author aseldawy
 *
 */
public class Helper {
	public static String[] MONTH_NAMES;
	public static String[] WEEKDAY_NAMES;
	
	static {
		MONTH_NAMES = new String[Calendar.DECEMBER+1];
		MONTH_NAMES[Calendar.JANUARY] = "Jan";
		MONTH_NAMES[Calendar.FEBRUARY] = "Feb";
		MONTH_NAMES[Calendar.MARCH] = "Mar";
		MONTH_NAMES[Calendar.APRIL] = "Apr";
		MONTH_NAMES[Calendar.MAY] = "May";
		MONTH_NAMES[Calendar.JUNE] = "Jun";
		MONTH_NAMES[Calendar.JULY] = "Jul";
		MONTH_NAMES[Calendar.AUGUST] = "Aug";
		MONTH_NAMES[Calendar.SEPTEMBER] = "Sep";
		MONTH_NAMES[Calendar.OCTOBER] = "Oct";
		MONTH_NAMES[Calendar.NOVEMBER] = "Nov";
		MONTH_NAMES[Calendar.DECEMBER] = "Dec";
		
		int max = Calendar.SATURDAY;
		max = Math.max(max, Calendar.SUNDAY);
		max = Math.max(max, Calendar.MONDAY);
		max = Math.max(max, Calendar.TUESDAY);
		max = Math.max(max, Calendar.WEDNESDAY);
		max = Math.max(max, Calendar.THURSDAY);
		max = Math.max(max, Calendar.FRIDAY);
		WEEKDAY_NAMES = new String[max+1];
		WEEKDAY_NAMES[Calendar.SATURDAY] = "Sat";
		WEEKDAY_NAMES[Calendar.SUNDAY] = "Sun";
		WEEKDAY_NAMES[Calendar.MONDAY] = "Mon";
		WEEKDAY_NAMES[Calendar.TUESDAY] = "Tue";
		WEEKDAY_NAMES[Calendar.WEDNESDAY] = "Wed";
		WEEKDAY_NAMES[Calendar.THURSDAY] = "Thu";
		WEEKDAY_NAMES[Calendar.FRIDAY] = "Fri";
	}
	
	/**
	 * Splits the given input around non-alphanumeric characters, word and number boundaries.
	 * Word boundary is the boundary between two consecutive characters one is Alpha and the other isn't.
	 * Number boundary is the same but with Numeric instead of Alpha
	 * @param input
	 * @return
	 */
	public static String[] split(String input) {
		// Holds the parts of the current input
		Vector parts = new Vector();
		// state values
		// 0- in a non-alphanumeric run
		// 1- in a word
		// 2- in a number
		byte state=0;
		// The first character in the current token. i.e. the first one to satisfy current state
		int start=0;
		// index to loop through the input
		int i;
		for (i=0;i<input.length();i++) {
			char ch = input.charAt(i);
			byte newState=0;
			if (Character.isDigit(ch))
				newState = 2;
			else if (Character.isLowerCase(ch) || Character.isUpperCase(ch))
				newState = 1;
			// The second condition is used to emit last token also
			if (state != newState) {
				// This check is useful for the first word only. If the input starts with a digit or an alpha
				// Remove the next condition if you want to return the boundaries also (punctuation marks)
				if (start != i && state != 0)
					parts.addElement(input.substring(start, i));
				// initialize for the next token
				state=newState;
				start = i;
			}
		}
		// emit last token
		if (start != i && state != 0)
			parts.addElement(input.substring(start, i));
		String[] arParts = new String[parts.size()];
		parts.copyInto(arParts);
		return arParts;
	}
	
	/**
	 * Splits the given string around the given separator returning an array of strings.
	 * @param input
	 * @param separator
	 * @return
	 */
	public static String[] split(String input, String separator) {
		Vector parts = new Vector();
		int i1 = 0;
		int i2;
		// keep copying parts as long as separator is found
		while ((i2 = input.indexOf(separator, i1)) != -1) {
			parts.addElement(input.substring(i1, i2));
			i1 = i2 + separator.length();
		}
		// add the last part which is not followed by a separator
		parts.addElement(input.substring(i1, input.length()));
		// copy into an array
		String[] partsArray = new String[parts.size()];
		parts.copyInto(partsArray);
		return partsArray;
	}
	
	/**
	 * Converts the given date to the following format
	 * <day of week> dd-MON-yyyy
	 * @param date
	 * @return
	 */
	public static String dateToString(long date) {
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(new Date(date));
		return WEEKDAY_NAMES[calendar.get(Calendar.DAY_OF_WEEK)]+" "+calendar.get(Calendar.DAY_OF_MONTH) + "-"
				+ MONTH_NAMES[calendar.get(Calendar.MONTH)] + "-"
				+ calendar.get(Calendar.YEAR);
	}
	
	/**
	 * Finds a month with the given name and returns its code in {@link Calendar}
	 * @param month - 
	 * @return
	 */
	private static int decodeMonth(String month) {
		month = month.toLowerCase();
		for (int i=0;i<MONTH_NAMES.length;i++) {
			if (month.startsWith(MONTH_NAMES[i].toLowerCase()))
				return i;
		}
		return -1;
	}
	
	/**
	 * Finds a day of week with the given name and returns its code in {@link Calendar}
	 * @param dayOfWeek
	 * @return
	 */
	private static int decodeDayOfWeek(String dayOfWeek) {
		dayOfWeek = dayOfWeek.toLowerCase();
		for (int i=0;i<WEEKDAY_NAMES.length;i++) {
			if (WEEKDAY_NAMES[i] != null
					&& dayOfWeek.startsWith(WEEKDAY_NAMES[i].toLowerCase()))
				return i;
		}
		return -1;
	}

	/**
	 * Decodes a date/time written in a human friendly way.
	 * @param humanFrienldy - A string written by a human.
	 * @return the given time written in milliseconds. -1 on failure.
	 */
	public static long decodeDate(String humanFriendly) {
		humanFriendly = humanFriendly.toLowerCase().trim();
		Calendar calendar = Calendar.getInstance();
		// We always set time to ZERO because we decode dates only
		calendar.set(Calendar.MILLISECOND, 0);
		calendar.set(Calendar.SECOND, 0);
		calendar.set(Calendar.MINUTE, 0);
		calendar.set(Calendar.HOUR, 0);
		if (humanFriendly.equals("now") || humanFriendly.equals("today")) {
			//do nothing
			return calendar.getTime().getTime();
		}
		if (humanFriendly.equals("yesterday")) {
			long millis = calendar.getTime().getTime();
			return millis - 24 * 60 * 60 * 1000;
		}
		if (humanFriendly.equals("tomorrow")) {
			long millis = calendar.getTime().getTime();
			return millis + 24 * 60 * 60 * 1000;
		}
		String[] parts = split(humanFriendly);
		int dayOfMonth=-1;
		int dayOfWeek=-1;
		boolean next = true;
		int month=-1;
		int year=-1;
		for (int i=0;i < parts.length; i++) {
			int temp;
			try {
				temp = Integer.parseInt(parts[i]);
				if (temp > 1000) //surely a year
					year = temp;
				else if (temp > 12) { //surely a day of month
					//if a previous ambiguous number <= 12 was used as dayOfMonth...
					//use it as month because the newer number is sure to by a dayOfMonth
					if (dayOfMonth != -1 && dayOfMonth <= 12)
						month = dayOfMonth - 1;
					dayOfMonth = temp;
				} else if (dayOfMonth == -1) //Default is that dayOfMonth appears before month (dd/mm)
					dayOfMonth = temp;
				else //we have previously found a dayOfMonth=>interpret this as a month
					month = temp - 1;
			} catch (NumberFormatException e) {
				//not a number
				//check if it is month name
				if ((temp = decodeMonth(parts[i])) != -1)
					month = temp;
				//check if it is day of week
				else if ((temp = decodeDayOfWeek(parts[i])) != -1)
					dayOfWeek = temp;
				//check if it is the word previous (used with day of week only)
				else if (parts[i].toLowerCase().startsWith("pre"))
					next = false;
				
			}
		}
		//check if at least a part of the date is set
		if (year != -1 || month != -1 || dayOfMonth != -1) {
			//set default values
			if (dayOfMonth == -1)
				dayOfMonth = 1;
			if (month == -1)
				month = 0;
			if (year == -1)
				year = calendar.get(Calendar.YEAR);
			calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth);
			calendar.set(Calendar.MONTH, month);
			calendar.set(Calendar.YEAR, year);
		} else if (dayOfWeek != -1){
			//discard all other values
			long increment = 24 * 60 * 60 * 1000 * (next?+1:-1);
			while (calendar.get(Calendar.DAY_OF_WEEK) != dayOfWeek)
				calendar.setTime(new Date(calendar.getTime().getTime() + increment));
		}
		return calendar.getTime().getTime();
	}

	public static int find(Object[] array, Object x) {
		for (int i=0;i<array.length;++i)
			if (array[i] == x)
				return i;
		return -1;
	}

	public static boolean writeRS(byte[] data, String rsname, boolean overwrite) {
		RecordStore rs;
		try {
			if (overwrite) {
				// Delete old record store if exists
				try {
					RecordStore.deleteRecordStore(rsname);
				} catch (Exception e) {
					// Exception occurs when record store does not exist
				}
			}
			// Open the record store and write to it
			rs = RecordStore.openRecordStore(rsname, overwrite);
			rs.addRecord(data, 0, data.length);
			rs.closeRecordStore();
			return true;
		} catch (RecordStoreFullException e) {
			e.printStackTrace();
		} catch (RecordStoreNotFoundException e) {
			e.printStackTrace();
		} catch (RecordStoreException e) {
			e.printStackTrace();
		}
		return false;
	}
	
	/**
	 * Write the given output stream to the recordstore with the given name
	 * @param baos
	 * @param rsname
	 */
	public static boolean writeRS(ByteArrayOutputStream baos, String rsname, boolean overwrite) {
		return writeRS(baos.toByteArray(), rsname, overwrite);
	}

	/**
	 * Reads the given rsname and returns a byte array
	 * @param rsname
	 * @return
	 */
	public static byte[] readRS(String rsname) {
		byte[] data = null;
		try {
			RecordStore rs = RecordStore.openRecordStore(rsname,false);
			for (RecordEnumeration re=rs.enumerateRecords(null,null,false);re.hasNextElement();) {
				data = re.nextRecord();
			}
			rs.closeRecordStore();
		} catch (RecordStoreFullException e) {
			e.printStackTrace();
		} catch (RecordStoreNotFoundException e) {
			//e.printStackTrace();
			//First time! no problem!
		} catch (RecordStoreException e) {
			e.printStackTrace();
		}
		return data;
	}

	/**
	 * Delete the record store with the given name (if exists)
	 * @param rsname
	 */
	public static void deleteRS(String rsname) {
		try {
			RecordStore.deleteRecordStore(rsname);
		} catch (Exception e) {
			// Exception occurs when record store does not exist
		}
	}
	
	/**
	 * Reads a text file and return it as text
	 * @param filename
	 * @return
	 */
	public static String readTextFile(String filename, String enc) {
		String text = "";
		try {
			InputStream is = filename.getClass().getResourceAsStream(filename);
			InputStreamReader isr = new InputStreamReader(is, enc);
			char [] cbuf = new char[4096];
			int bufferSize;
			while ((bufferSize = isr.read(cbuf)) > 0) {
				text += new String(cbuf, 0, bufferSize);
			}
			is.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return text;
	}

	public static String formatTime(long time) {
//		int millis = (int) (time % 1000);
		time /= 1000;
//		int seconds = (int) (time % 60);
		time /= 60;
		int minutes = (int) (time % 60);
		time /= 60;
		int hours = (int) time;
		String formattedString = "";
		formattedString += hours;
		formattedString += ":";
		if (minutes < 10)
			formattedString += "0";
		formattedString += minutes;
		return formattedString;
	}
	
	public static boolean arrayEqual(boolean[] ar1, boolean[] ar2) {
		if (ar1.length != ar2.length)
			return false;
		int i=0;
		while (i < ar1.length && ar1[i] == ar2[i])
			i++;
		return i == ar1.length;
	}
	
	public static void displayError(String title, Throwable e) {
		e.printStackTrace();
		Alert alert = new Alert(title, e.toString(), null, AlertType.ERROR);
		MainScreen.display.setCurrent(alert);
	}

	/**
	 * Writes any object to the output stream.
	 * @param dos
	 * @param value
	 * @throws IOException
	 */
	public static void writeObject(DataOutputStream dos, Object value) throws IOException {
		dos.writeUTF(value.getClass().getName());
		if (value instanceof String) {
			dos.writeUTF((String)value);
		} else if (value instanceof Integer) {
			dos.writeInt(((Integer)value).intValue());
		} else if (value instanceof Float) {
			dos.writeFloat(((Float)value).floatValue());
		} else if (value instanceof Boolean) {
			dos.writeBoolean(((Boolean)value).booleanValue());
		} else if (value instanceof Vector){
			Vector vector = (Vector) value;
			// Write stack size
			dos.writeInt(vector.size());
			// Write all objects in the vector recursively
			for (Enumeration e = vector.elements(); e.hasMoreElements();) {
				writeObject(dos, e.nextElement());
			}
		} else if (value instanceof Serializable){
			((Serializable)value).serialize(dos);
		} else {
			throw new IOException("Cannot serialize this object: " + value
					+ " of class: " + value.getClass().getName());
	}
	}

	/**
	 * Reads any object out of the given input stream.
	 * This object should be written using {@link #writeObject(DataOutputStream, Object)}
	 * @param dis
	 * @return
	 * @throws IOException 
	 */
	public static Object readObject(DataInputStream dis) throws IOException {
		String type = dis.readUTF();
		if (type.equals("java.lang.String")) {
			return dis.readUTF();
		} else if (type.equals("java.lang.Integer")) {
			return new Integer(dis.readInt());
		} else if (type.equals("java.lang.Float")) {
			return new Float(dis.readFloat());
		} else if (type.equals("java.lang.Boolean")) {
			return new Boolean(dis.readBoolean());
		} else if (type.equals("java.util.Stack") || type.equals("java.util.Vector")) {
			Vector vector = type.equals("java.util.Stack") ? new Stack() : new Vector();
			int size = dis.readInt();
			while (size-- > 0) {
				vector.addElement(readObject(dis));
			}
			return vector;
		} else {
			throw new IOException("Cannot read a value of type: "+type);
		}
	}
}
